P3195 [HNOI2008]玩具装箱TOY

闲扯

早就想学 $DP$ 的优化了,咕了好久,今天终于还是学了学。

题面

P3195 [HNOI2008]玩具装箱TOY

Solution

考虑暴力 $DP$ 。

很容易可以得到一个递推式: $dp_i=\min_{j=0}^{i-1}(dp_j+(i-j+1+sum_i-sum_j-L)^2)$ 。

如果直接 $DP$ ,复杂度是 $n^2$ 的,显然不行,考虑优化。

由于原式中有平方项,所以会出现和 $i,j$ 均有关的转移项,普通的单调队列优化显然不行。

怎么办?这就要用到今天新学到的算法——斜率优化

为了方便计算,我们做出如下约定: $a_i=i+sum_i,b_i=i+sum_i+L+1$ 。

考虑将原式中的 $\min$ 去掉,则:

$dp_i=dp_j+(a_i-b_j)^2$

$dp_i=dp_j+a_i^2-2a_ib_j+b_j^2$

$dp_j+b_j^2=2a_ib_j+dp_i-a_i^2$

我们将 $b_j$ 看作 $x$ ,将 $dp_j+b_j^2$ 看作 $y$ ,那么我们就得到了一个斜率为 $2*a_i$ 的直线。

那么我们要求的 $dp_i$ 就变成了这条直线的截距加上一个 $a_i^2$ 。

所以我们实际要求的是截距的最小值。

因为 $a_i$ 是单增的且求的是最小值,画图可以看出我们需要维护的是一个下凸壳。

我们每次将斜率小于 $2*a_i$ 的从单调队列中弹掉,那么队首就是我们要找的 $j$ 。

插入一个新的点时,我们将斜率大于该点与队尾的点构成的斜率的点弹出,再加入该点即可。(因为斜率是单增的,所以之后一定不会访问到这个点)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include<bits/stdc++.h>
#define del(a,i) memset(a,i,sizeof(a))
#define ll long long
#define inl inline
#define il inl void
#define it inl int
#define ill inl ll
#define re register
#define ri re int
#define rl re ll
#define mid ((l+r)>>1)
#define lowbit(x) (x&(-x))
#define INF 0x3f3f3f3f
using namespace std;
template<class T>il read(T &x){
int f=1;char k=getchar();x=0;
for(;k>'9'||k<'0';k=getchar()) if(k=='-') f=-1;
for(;k>='0'&&k<='9';k=getchar()) x=(x<<3)+(x<<1)+k-'0';
x*=f;
}
template<class T>il print(T x){
if(x/10) print(x/10);
putchar(x%10+'0');
}
ll mul(ll a,ll b,ll mod){long double c=1.;return (a*b-(ll)(c*a*b/mod)*mod)%mod;}
it qpow(int x,int m,int mod){
int res=1,bas=x%mod;
while(m){
if(m&1) res=(1ll*res*bas)%mod;
bas=(1ll*bas*bas)%mod,m>>=1;
}
return res%mod;
}
const int MAXN = 5e4+5;
int n,L,val[MAXN],q[MAXN],head=1,tail=1;
ll sum[MAXN],dp[MAXN];
#define a(i) (i+sum[i])
#define b(i) (i+sum[i]+L+1)
#define x(i) (b(i))
#define y(i) (dp[i]+b(i)*b(i))
double slope(int x,int y){return 1.*(y(y)-y(x))/(x(y)-x(x));}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(n),read(L);
for(ri i=1;i<=n;++i) read(val[i]),sum[i]=sum[i-1]+val[i];
for(ri i=1;i<=n;++i){
while(head<tail&&slope(q[head],q[head+1])<2*a(i)) ++head;
dp[i]=dp[q[head]]+(a(i)-b(q[head]))*(a(i)-b(q[head]));
while(head<tail&&slope(i,q[tail-1])<slope(q[tail-1],q[tail])) --tail;
q[++tail]=i;
}
print(dp[n]);
return 0;
}

总结

斜率优化感觉还没有入门啊,还要多最一些练习才行。